package com.bsj.power.common.util;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;

/**
 * 加密
 *
 * @author
 */
@Slf4j
public class HashUtil {

    /**
     * 静态块
     * 引入BC
     * Cipher.getInstance("DES/ECB/PKCS5Padding", "BC")
     */
    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

	/**
	 * MD5加密
	 * @param content 待加密文本
	 * @return
	 */
	public static String MD5(Object content) {
		return encryption("MD5", content);
	}

	/**
	 * SHA-1加密
	 * @param content 待加密文本
	 * @return
	 */
	public static String SHA1(Object content) {
		return encryption("SHA-1", content);
	}

	/**
	 * SHA-256加密
	 * @param content 待加密文本
	 * @return
	 */
	public static String SHA256(Object content) {
		return encryption("SHA-256", content);
	}

	/**
	 * MD5、SHA-1 等通用加密方法
	 * @param type 加密方法
	 * @param content 待加密文本
	 * @return
	 */
	private static String encryption(String type, Object content) {
		try {
			MessageDigest md = MessageDigest.getInstance(type);
			md.update(content.toString().getBytes("UTF-8"));
			byte[] hash = md.digest();
			StringBuilder secpwd = new StringBuilder();
			for (int i = 0; i < hash.length; i++) {
				int v = hash[i] & 0xFF;
				if (v < 16) {
					secpwd.append(0);
				}
				secpwd.append(Integer.toString(v, 16));
			}
			return secpwd.toString();
		} catch (Exception e) {
			log.error("加密失败：" + ExceptionUtil.getStackStr(e));
		}
		return "";
	}

	private static final String SEPARATOR = "::";
	private static final int SALT_BYTE_SIZE = 8;
	private static final int HASH_BYTE_SIZE = 16;
	private static final int PBKDF2_ITERATIONS = 1024;

	/**
	 * 密码自动加盐加密
	 * @param password 用户的密码
	 * @return 加密后的密码（和盐）
	 */
	public static String createHash(String password) {
		SecureRandom random = new SecureRandom();
		byte[] salt = new byte[SALT_BYTE_SIZE];
		random.nextBytes(salt);
		byte[] hash = pbkdf2(password.toCharArray(), salt, HASH_BYTE_SIZE);
		return toHex(salt) + SEPARATOR + toHex(hash);
	}

	/**
	 * 验证密码是否正确
	 * @param password 用户输入的原始密码
	 * @param correctHash 带盐的密码
	 * @return 是否验证成功
	 */
	public static boolean validateHash(String password, String correctHash) {
		String[] params = correctHash.split(SEPARATOR);
		byte[] salt = fromHex(params[0]);
		byte[] hash = fromHex(params[1]);
		byte[] testHash = pbkdf2(password.toCharArray(), salt, hash.length);
		return slowEquals(hash, testHash);
	}

	private static boolean slowEquals(byte[] a, byte[] b) {
		int diff = a.length ^ b.length;
		for (int i = 0; i < a.length && i < b.length; i++) {
			diff |= a[i] ^ b[i];
		}
		return diff == 0;
	}

	private static byte[] pbkdf2(char[] password, byte[] salt, int bytes) {
		PBEKeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, bytes * 8);
		try {
			SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
			return skf.generateSecret(spec).getEncoded();
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			log.error(ExceptionUtil.getStackStr(e));
		}
		return null;
	}

	private static byte[] fromHex(String hex) {
		byte[] binary = new byte[hex.length() / 2];
		for (int i = 0; i < binary.length; i++) {
			binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
		}
		return binary;
	}

	private static String toHex(byte[] array) {
		BigInteger bi = new BigInteger(1, array);
		String hex = bi.toString(16);
		int paddingLength = (array.length * 2) - hex.length();
		if (paddingLength > 0) {
			return String.format("%0" + paddingLength + "d", 0) + hex;
		} else {
			return hex;
		}
	}

	public static byte[] HMACSHA256(String data, String key) throws Exception {
		Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
		sha256_HMAC.init(secret_key);
		byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
		return array;
	}

	public static String HMACSHA256Str(String data, String key) throws Exception {
		Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
		sha256_HMAC.init(secret_key);
		byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * AES算法加密明文(AES-128-CBC加密模式)
	 * @param key 秘钥
	 * @param iv 偏移量
	 * @param data 待加密数据
	 * @return
	 * @throws Exception
	 */
	public static String encryptAES(String key, String iv, String data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			int blockSize = cipher.getBlockSize();
			byte[] dataBytes = data.getBytes();
			int plaintextLength = dataBytes.length;

			if (plaintextLength % blockSize != 0) {
				plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
			}

			byte[] plaintext = new byte[plaintextLength];
			System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
			// CBC模式，需要一个向量iv，可增加加密算法的强度
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

			cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
			byte[] encrypted = cipher.doFinal(plaintext);

			// BASE64做转码。
			return encode(encrypted).trim();

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public static String encryptAESPkcs(String key, String iv, String data) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			int blockSize = cipher.getBlockSize();
			byte[] dataBytes = data.getBytes();
			int plaintextLength = dataBytes.length;

			if (plaintextLength % blockSize != 0) {
				plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
			}

			byte[] plaintext = new byte[plaintextLength];
			System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
			// CBC模式，需要一个向量iv，可增加加密算法的强度
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

			cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
			byte[] encrypted = cipher.doFinal(plaintext);

			// BASE64做转码。
			return encode(encrypted).trim();

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * AES算法解密密文
	 * @param key 秘钥
	 * @param iv 偏移量
	 * @param data 待加密数据
	 * @return
	 * @throws Exception
	 */
	public static String decryptAES(String key, String iv, String data) {
		try {
			//先用base64解密
			byte[] encrypted1 = decode(data);

			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

			cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

			byte[] original = cipher.doFinal(encrypted1);
			String originalString = new String(original);
			return originalString.trim();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 编码
	 * @param byteArray
	 * @return
	 */
	public static String encode(byte[] byteArray) {
		return new String(new Base64().encode(byteArray));
	}

	/**
	 * 解码
	 * @param base64EncodedString
	 * @return
	 */
	public static byte[] decode(String base64EncodedString) {
		return new Base64().decode(base64EncodedString);
	}



    /**
     * ZF261 建行Mac解密方法
     * @param content
     * @param pubKey
     * @return
     * @throws Exception
     */
    public static String encryptDESPkcs(String content, String pubKey) throws Exception {
        String encryptKey = pubKey.substring(0, 8);
        String tempString = URLDecoder.decode(content.substring(0, content.length() - 15), StandardCharsets.UTF_8);
        String basedString = tempString.replaceAll(",", "+");
        byte[] tempBytes = new Base64().decode(basedString);
        SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(StandardCharsets.UTF_8), "DES");
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding", "BC");
        cipher.init(2, key);
        byte[] cipherText = cipher.doFinal(tempBytes);
        String str = new String(cipherText, StandardCharsets.UTF_8);
        return str.substring(0, str.indexOf("&SIGN="));
    }
}
